package org.lds.hotkey.console.business.appinstance.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.lds.hotkey.common.autoconfigure.etcd.EtcdService;
import org.lds.hotkey.common.constant.KeyConstant;
import org.lds.hotkey.common.model.AppInstanceModel;
import org.lds.hotkey.common.model.dto.ResultDTO;
import org.lds.hotkey.common.model.pojo.OrderBy;
import org.lds.hotkey.common.model.pojo.PageResult;
import org.lds.hotkey.common.util.AssertUtil;
import org.lds.hotkey.common.util.JsonUtil;
import org.lds.hotkey.console.business.app.service.AppService;
import org.lds.hotkey.console.business.appinstance.converter.*;
import org.lds.hotkey.console.business.appinstance.entity.AppInstance;
import org.lds.hotkey.console.business.appinstance.mapper.AppInstanceMapper;
import org.lds.hotkey.console.business.appinstance.model.request.*;
import org.lds.hotkey.console.business.appinstance.model.response.*;
import org.lds.hotkey.console.business.appinstance.service.AppInstanceBaseService;
import org.lds.hotkey.console.common.enums.ConsoleErrorEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 基础服务实现类
 *
 * @author lidongsheng
 * @since 2024-06-05
 */
@Slf4j
@Service
public class AppInstanceBaseServiceImpl implements AppInstanceBaseService {

    @Autowired
    private AppInstanceMapper appInstanceMapper;

    @Autowired
    private AppService appService;

    @Autowired
    private EtcdService etcdService;

    @Autowired
    private AppInstanceBaseCreateRequestToEntityConverter appInstanceBaseCreateRequestToEntityConverter;

    @Autowired
    private AppInstanceBaseUpdateRequestToEntityConverter appInstanceBaseUpdateRequestToEntityConverter;

    @Autowired
    private AppInstanceBaseListRequestToEntityConverter appInstanceBaseListRequestToEntityConverter;

    @Autowired
    private AppInstanceToBaseResponseConverter appInstanceToBaseResponseConverter;

    @Autowired
    private AppInstanceBaseListPageRequestToEntityConverter appInstanceBaseListPageRequestToEntityConverter;

    @Autowired
    private AppInstanceBaseGetByKeyRequestToEntityConverter appInstanceBaseGetByKeyRequestToEntityConverter;

    @Autowired
    private AppInstanceBaseDeleteRequestToEntityConverter appInstanceBaseDeleteRequestToEntityConverter;

    @Override
    @Transactional
    public ResultDTO<AppInstanceBaseResponse> createAppInstance(AppInstanceBaseCreateRequest request) {
        // 检查app是否存在
        appService.existApp(request.getAppId());

        try {
            AppInstance appInstance = appInstanceBaseCreateRequestToEntityConverter.convert(request);

            appInstanceMapper.insert(appInstance);

            // 插入成功则同步到ETCD
            boolean success = etcdService.put(KeyConstant.INSTANCE_DIRECTORY + appInstance.getAppId() + "/" + appInstance.getId(),
                    JsonUtil.toJson(AppInstanceModel.builder().id(appInstance.getId()).ip(appInstance.getIp()).port(appInstance.getPort()).build()));
            AssertUtil.assertTrue(success, ConsoleErrorEnum.ETCD_SYNC_FAIL);

            AppInstanceBaseResponse response = appInstanceToBaseResponseConverter.convert(appInstance);
            return ResultDTO.success(response);
        } catch (DuplicateKeyException e) {
            return ResultDTO.fail("DATA_REPEAT", "数据重复");
        }
    }

    @Transactional
    @Override
    public ResultDTO<AppInstanceBaseBatchCreateResponse> batchCreateAppInstance(AppInstanceBaseBatchCreateRequest request) {
        AppInstanceBaseBatchCreateResponse response = AppInstanceBaseBatchCreateResponse.builder()
                .responses(request.getRequests().stream().map(this::createAppInstance).collect(Collectors.toList()))
                .build();
        return ResultDTO.success(response);
    }

    @Transactional
    @Override
    public ResultDTO<AppInstanceBaseDeleteResponse> deleteAppInstance(AppInstanceBaseDeleteRequest request) {
        AppInstanceBaseDeleteResponse response = AppInstanceBaseDeleteResponse.builder().request(request).build();

        AppInstance appInstance = appInstanceBaseDeleteRequestToEntityConverter.convert(request);
        List<AppInstance> appInstances = appInstanceMapper.selectList(new QueryWrapper<>(appInstance));
        if (CollectionUtils.isEmpty(appInstances)) return ResultDTO.success(response);

        appInstanceMapper.delete(new QueryWrapper<>(appInstance));

        // 删除成功则同步到ETCD
        for (AppInstance currAppInstance : appInstances) {
            boolean success = etcdService.delete(KeyConstant.INSTANCE_DIRECTORY + currAppInstance.getAppId() + "/" + currAppInstance.getId());
            AssertUtil.assertTrue(success, ConsoleErrorEnum.ETCD_SYNC_FAIL);
        }

        return ResultDTO.success(response);
    }

    @Transactional
    @Override
    public ResultDTO<AppInstanceBaseBatchDeleteResponse> batchDeleteAppInstance(AppInstanceBaseBatchDeleteRequest request) {
        AppInstanceBaseBatchDeleteResponse response = AppInstanceBaseBatchDeleteResponse.builder()
                .responses(request.getRequests().stream().map(this::deleteAppInstance).collect(Collectors.toList()))
                .build();
        return ResultDTO.success(response);
    }

    @Transactional
    @Override
    public ResultDTO<AppInstanceBaseResponse> updateAppInstance(AppInstanceBaseUpdateRequest request) {
        AppInstance appInstance = appInstanceBaseUpdateRequestToEntityConverter.convert(request);
        AppInstanceBaseResponse response = appInstanceToBaseResponseConverter.convert(appInstance);

        AppInstance currAppInstance = appInstanceMapper.selectById(request.getId());
        if (currAppInstance == null) ResultDTO.success(response);

        try {
            appInstanceMapper.updateById(appInstance);

            boolean success = updateETCD(request, currAppInstance);
            AssertUtil.assertTrue(success, ConsoleErrorEnum.ETCD_SYNC_FAIL);

            return ResultDTO.success(response);
        } catch (DuplicateKeyException e) {
            return ResultDTO.fail("DATA_REPEAT", "数据重复");
        }
    }

    private boolean updateETCD(AppInstanceBaseUpdateRequest request, AppInstance currAppInstance) {
        if (!currAppInstance.getEnable() && !request.getEnable()) return true;
        if (currAppInstance.getEnable() && BooleanUtils.isFalse(request.getEnable())) {
            return etcdService.delete(KeyConstant.INSTANCE_DIRECTORY + currAppInstance.getAppId() + "/" + currAppInstance.getId());
        }
        return etcdService.put(KeyConstant.INSTANCE_DIRECTORY + currAppInstance.getAppId() + "/" + currAppInstance.getId(),
                JsonUtil.toJson(AppInstanceModel.builder()
                        .id(request.getId())
                        .ip(Objects.nonNull(request.getIp()) ? request.getIp() : currAppInstance.getIp())
                        .port(Objects.nonNull(request.getPort()) ? request.getPort() : currAppInstance.getPort())
                        .build()));
    }

    @Transactional
    @Override
    public ResultDTO<AppInstanceBaseBatchUpdateResponse> batchUpdateAppInstance(AppInstanceBaseBatchUpdateRequest request) {
        AppInstanceBaseBatchUpdateResponse response = AppInstanceBaseBatchUpdateResponse.builder()
                .requests(request.getRequests().stream().map(this::updateAppInstance).collect(Collectors.toList()))
                .build();
        return ResultDTO.success(response);
    }

    @Override
    public ResultDTO<AppInstanceBaseResponse> getAppInstanceById(AppInstanceBaseGetByIdRequest request) {
        AppInstance appInstance = appInstanceMapper.selectById(request.getId());
        if (appInstance == null) {
            return ResultDTO.success();
        }

        AppInstanceBaseResponse response = appInstanceToBaseResponseConverter.convert(appInstance);
        return ResultDTO.success(response);
    }

    @Override
    public ResultDTO<AppInstanceBaseListResponse> getAppInstanceByIds(AppInstanceBaseGetByIdsRequest request) {
        List<AppInstance> list = request.getIds().stream().map(id -> appInstanceMapper.selectById(id)).collect(Collectors.toList());
        return ResultDTO.success(AppInstanceBaseListResponse.builder()
                .appInstances(list.stream().map(appInstance -> appInstanceToBaseResponseConverter.convert(appInstance)).collect(Collectors.toList()))
                .build());
    }

    @Override
    public AppInstance getAppInstanceByKey(AppInstanceBaseGetByKeyRequest request) {
        return appInstanceMapper.selectOne(new QueryWrapper<>(appInstanceBaseGetByKeyRequestToEntityConverter.convert(request)));
    }

    @Override
    public ResultDTO<AppInstanceBaseListResponse> listAppInstance(AppInstanceBaseListRequest request) {
        QueryWrapper<AppInstance> wrapper = new QueryWrapper<>(appInstanceBaseListRequestToEntityConverter.convert(request));
        if (StringUtils.isNotEmpty(request.getOrderByKey())) {
            wrapper.orderByDesc(request.getOrderByKey());
        }

        List<AppInstance> appInstances = appInstanceMapper.selectList(wrapper);

        AppInstanceBaseListResponse response = AppInstanceBaseListResponse.builder()
                .appInstances(appInstances.stream().map(appInstanceToBaseResponseConverter::convert).collect(Collectors.toList()))
                .build();
        return ResultDTO.success(response);
    }

    @Override
    public ResultDTO<AppInstanceBaseListResponse> listAllAppInstance(OrderBy orderBy) {
        AppInstanceBaseListRequest appInstanceRequest = AppInstanceBaseListRequest.builder().build();
        appInstanceRequest.setOrderByKey(orderBy.getOrderByKey());
        return listAppInstance(appInstanceRequest);
    }

    @Override
    public ResultDTO<PageResult<AppInstanceBaseResponse>> listAppInstancePage(AppInstanceBaseListPageRequest request) {
        Page<AppInstance> page = new Page<>(request.getPageNum(), request.getPageSize(), true);

        QueryWrapper<AppInstance> wrapper = new QueryWrapper<>(appInstanceBaseListPageRequestToEntityConverter.convert(request));
        if (Objects.nonNull(request.getGtId())) {
            wrapper.gt("id", request.getGtId());
        }

        if (StringUtils.isNotEmpty(request.getOrderByKey())) {
            if (request.getOrderByDesc() != null && request.getOrderByDesc().booleanValue() == false) {
                wrapper.orderByAsc(request.getOrderByKey());
            } else {
                wrapper.orderByDesc(request.getOrderByKey());
            }
        }

        IPage<AppInstance> pageResult = appInstanceMapper.selectPage(page, wrapper);

        PageResult<AppInstanceBaseResponse> response = new PageResult(
                pageResult.getTotal(), pageResult.getPages(), pageResult.getCurrent(), pageResult.getSize(),
                pageResult.getRecords().stream().map(appInstanceToBaseResponseConverter::convert).collect(Collectors.toList()));
        return ResultDTO.success(response);
    }

    @Override
    public ResultDTO<AppInstanceBaseBatchCreateResponse> batchCreateAppInstanceAsync(AppInstanceBaseBatchCreateRequest request) {
        AppInstanceBaseBatchCreateResponse response = AppInstanceBaseBatchCreateResponse.builder()
                .responses(request.getRequests().parallelStream().map(this::createAppInstance).collect(Collectors.toList()))
                .build();
        return ResultDTO.success(response);
    }

    @Override
    public ResultDTO<AppInstanceBaseBatchDeleteResponse> batchDeleteAppInstanceAsync(AppInstanceBaseBatchDeleteRequest request) {
        AppInstanceBaseBatchDeleteResponse response = AppInstanceBaseBatchDeleteResponse.builder()
                .responses(request.getRequests().parallelStream().map(this::deleteAppInstance).collect(Collectors.toList()))
                .build();
        return ResultDTO.success(response);
    }

    @Override
    public ResultDTO<AppInstanceBaseBatchUpdateResponse> batchUpdateAppInstanceAsync(AppInstanceBaseBatchUpdateRequest request) {
        AppInstanceBaseBatchUpdateResponse response = AppInstanceBaseBatchUpdateResponse.builder()
                .requests(request.getRequests().parallelStream().map(this::updateAppInstance).collect(Collectors.toList()))
                .build();
        return ResultDTO.success(response);
    }

}
